Visualization in Big Data Context

Learning Objectives

By the end of this section, you will be able to:

  1. Understand the challenges of visualizing big data
  2. Create effective visualizations with ggplot2 for large datasets
  3. Build interactive visualizations using plotly
  4. Implement sampling and aggregation strategies for big data visualization
  5. Compare different visualization approaches and tools

Introduction

Visualization is a critical component of data analysis, especially in the context of big data. While visualization helps uncover patterns and insights, working with large datasets presents unique challenges such as overplotting, performance issues, and information overload. In this section, we’ll explore strategies and tools to effectively visualize large datasets using R.

1. Challenges of Visualizing Big Data

Visualizing big data comes with several challenges that require special consideration:

1.1 Overplotting

When too many points overlap in a scatter plot, patterns become obscured. This is particularly problematic with datasets containing millions of observations.

1.2 Performance Issues

Rendering millions of points can be computationally intensive and slow, both in terms of generation and interactivity.

1.3 Memory Constraints

Large datasets may exceed available memory when creating complex visualizations.

1.4 Information Overload

Too much information in a single visualization can make it difficult to extract meaningful insights.

1.5 Scalability

Visualizations need to remain effective and readable at different data scales.

Strategies for Big Data Visualization

To address these challenges, we employ several strategies:

  1. Sampling: Use representative subsets of data
  2. Aggregation: Summarize data before visualization
  3. Binning: Group data points into bins or hexagons
  4. Density Estimation: Show data density rather than individual points
  5. Interactive Exploration: Allow users to drill down into details
  6. Progressive Rendering: Show data in stages

2. Loading and Preparing the Dataset

For this training, we’ll use the Diamonds Dataset which contains 53,940 observations of diamond characteristics. While not “big data” by modern standards, it’s large enough to demonstrate visualization challenges and techniques.

# Load the diamonds dataset (comes with ggplot2)
data(diamonds)

# Convert to data.table for efficient manipulation
diamonds_dt <- as.data.table(diamonds)

# Create a larger version by sampling with replacement (for demonstration)
set.seed(123)
big_diamonds <- diamonds_dt[sample(.N, 100000, replace = TRUE)]

# Add some derived columns
big_diamonds[, price_per_carat := price / carat]
big_diamonds[, log_price := log10(price)]
big_diamonds[, size_category := cut(carat, 
                                     breaks = c(0, 0.5, 1, 1.5, 2, 5),
                                     labels = c("Tiny", "Small", "Medium", "Large", "Very Large"))]

# Save to file for consistency
fwrite(big_diamonds, "data/big_diamonds.csv")

# Display dataset information
cat("Dataset Information:\n")
## Dataset Information:
cat("Number of rows:", format(nrow(big_diamonds), big.mark = ","), "\n")
## Number of rows: 100,000
cat("Number of columns:", ncol(big_diamonds), "\n\n")
## Number of columns: 13
cat("First few rows:\n")
## First few rows:
DT::datatable(big_diamonds[1:10, ], 
              options = list(pageLength = 10, scrollX = TRUE))

Dataset Structure

# Display structure
cat("Dataset Structure:\n")
## Dataset Structure:
str(big_diamonds[, 1:8])
## Classes 'data.table' and 'data.frame':   100000 obs. of  8 variables:
##  $ carat  : num  0.73 0.7 0.31 0.31 0.31 0.83 0.51 0.7 0.4 1.1 ...
##  $ cut    : Ord.factor w/ 5 levels "Fair"<"Good"<..: 5 5 5 5 5 2 3 2 5 3 ...
##  $ color  : Ord.factor w/ 7 levels "D"<"E"<"F"<"G"<..: 6 4 1 5 2 2 1 5 2 6 ...
##  $ clarity: Ord.factor w/ 8 levels "I1"<"SI2"<"SI1"<..: 5 5 5 7 8 3 4 3 5 3 ...
##  $ depth  : num  60.7 60.8 61.6 62.2 60.9 63.7 62.5 64.2 61.6 61.2 ...
##  $ table  : num  56 56 55 56 55 59 58 58 56 61 ...
##  $ price  : int  2397 3300 713 707 987 3250 1668 1771 1053 4640 ...
##  $ x      : num  5.85 5.73 4.3 4.34 4.39 5.95 5.12 5.59 4.73 6.61 ...
##  - attr(*, ".internal.selfref")=<externalptr>
# Summary statistics
cat("\n\nSummary Statistics:\n")
## 
## 
## Summary Statistics:
summary_stats <- big_diamonds[, .(
  Observations = .N,
  Unique_Cuts = length(unique(cut)),
  Unique_Colors = length(unique(color)),
  Unique_Clarity = length(unique(clarity)),
  Avg_Price = mean(price),
  Avg_Carat = mean(carat),
  Max_Price = max(price),
  Min_Price = min(price)
)]

print(summary_stats)
##    Observations Unique_Cuts Unique_Colors Unique_Clarity Avg_Price Avg_Carat
##           <int>       <int>         <int>          <int>     <num>     <num>
## 1:       100000           5             7              8  3931.789 0.7974344
##    Max_Price Min_Price
##        <int>     <int>
## 1:     18823       326

3. Base R Graphics for Quick Exploration

Before diving into ggplot2, let’s review base R graphics which can be useful for quick exploratory analysis.

# Set up multi-panel plot
par(mfrow = c(2, 2), mar = c(4, 4, 2, 1))

# 1. Histogram of price
hist(big_diamonds$price, breaks = 50, 
     main = "Distribution of Diamond Prices",
     xlab = "Price (USD)", 
     ylab = "Frequency",
     col = "steelblue",
     border = "white")

# 2. Boxplot of price by cut
boxplot(price ~ cut, data = big_diamonds,
        main = "Price by Diamond Cut",
        xlab = "Cut Quality",
        ylab = "Price (USD)",
        col = brewer.pal(5, "Set2"),
        outline = FALSE)

# 3. Scatter plot (sampled for clarity)
sample_indices <- sample(nrow(big_diamonds), 1000)
plot(big_diamonds$carat[sample_indices], 
     big_diamonds$price[sample_indices],
     main = "Carat vs Price (1,000 points)",
     xlab = "Carat",
     ylab = "Price (USD)",
     pch = 16,
     col = alpha("darkred", 0.3),
     cex = 0.8)

# 4. Bar plot of cuts
cut_counts <- table(big_diamonds$cut)
barplot(cut_counts,
        main = "Distribution of Diamond Cuts",
        xlab = "Cut",
        ylab = "Count",
        col = brewer.pal(5, "Set3"),
        border = NA)

# Reset par
par(mfrow = c(1, 1))

4. Introduction to ggplot2

ggplot2 is a powerful visualization package based on the Grammar of Graphics. It provides a consistent and flexible framework for creating complex visualizations.

4.1 Basic ggplot2 Components

Every ggplot2 visualization consists of:

  1. Data: The dataset being visualized
  2. Aesthetics (aes): Mappings from data to visual properties
  3. Geometries (geom): The type of plot (points, lines, bars, etc.)
  4. Scales: Control of aesthetic mappings
  5. Facets: Create multiple plots based on data subsets
  6. Themes: Control non-data elements (background, grids, etc.)

4.2 Basic Plot Types

Scatter Plot

# Basic scatter plot
p_scatter <- ggplot(big_diamonds[sample(.N, 5000)],  # Sample for clarity
                    aes(x = carat, y = price, color = cut)) +
  geom_point(alpha = 0.6, size = 1.5) +
  labs(title = "Diamond Price vs Carat by Cut",
       subtitle = "Sample of 5,000 diamonds",
       x = "Carat",
       y = "Price (USD)",
       color = "Cut Quality") +
  scale_color_brewer(palette = "Set2") +
  theme_minimal()

print(p_scatter)

Histogram

# Histogram with density overlay
p_hist <- ggplot(big_diamonds, aes(x = price)) +
  geom_histogram(aes(y = ..density..),
                 bins = 50,
                 fill = "steelblue",
                 alpha = 0.7) +
  geom_density(color = "darkred", size = 1) +
  labs(title = "Distribution of Diamond Prices",
       subtitle = "With density curve overlay",
       x = "Price (USD)",
       y = "Density") +
  scale_x_continuous(labels = dollar) +
  theme_minimal()

print(p_hist)

Box Plot

# Box plot with jitter
p_box <- ggplot(big_diamonds, aes(x = cut, y = price, fill = cut)) +
  geom_boxplot(outlier.alpha = 0.3, outlier.size = 1) +
  geom_jitter(alpha = 0.05, width = 0.2, size = 0.5) +
  labs(title = "Price Distribution by Diamond Cut",
       x = "Cut Quality",
       y = "Price (USD)") +
  scale_fill_brewer(palette = "Set2") +
  scale_y_continuous(labels = dollar) +
  theme(legend.position = "none")

print(p_box)

Bar Plot

# Bar plot
p_bar <- ggplot(big_diamonds, aes(x = cut, fill = cut)) +
  geom_bar() +
  labs(title = "Count of Diamonds by Cut Quality",
       x = "Cut Quality",
       y = "Count") +
  scale_fill_brewer(palette = "Set3") +
  theme(legend.position = "none") +
  coord_flip()

print(p_bar)

4.3 Handling Large Datasets with ggplot2

Strategy 1: Sampling

# Compare different sampling approaches
set.seed(123)
samples <- list(
  random_1k = big_diamonds[sample(.N, 1000)],
  random_5k = big_diamonds[sample(.N, 5000)],
  stratified = big_diamonds[, .SD[sample(.N, min(200, .N))], by = cut]
)

# Create comparison plots
p_sampling_comparison <- ggplot(samples$random_5k, 
                                aes(x = carat, y = price, color = cut)) +
  geom_point(alpha = 0.3, size = 1) +
  geom_smooth(method = "lm", se = FALSE, size = 0.5) +
  labs(title = "Sampling Strategy: 5,000 Random Points",
       subtitle = "With linear trend lines by cut",
       x = "Carat",
       y = "Price (USD)") +
  scale_color_brewer(palette = "Set2") +
  facet_wrap(~cut, nrow = 1) +
  theme(legend.position = "none")

print(p_sampling_comparison)

Strategy 2: Binning (2D Histogram)

# 2D histogram for dense data
p_bin2d <- ggplot(big_diamonds, aes(x = carat, y = price)) +
  geom_bin2d(bins = 50) +
  scale_fill_viridis(option = "plasma", trans = "log10") +
  labs(title = "2D Histogram: Carat vs Price",
       subtitle = "Using binning to handle dense data",
       x = "Carat",
       y = "Price (USD)",
       fill = "Count\n(log10)") +
  theme_minimal()

print(p_bin2d)

Strategy 3: Hexagonal Binning

# Hexagonal binning
p_hex <- ggplot(big_diamonds, aes(x = carat, y = price)) +
  geom_hex(bins = 40) +
  scale_fill_viridis(option = "magma", trans = "log10") +
  labs(title = "Hexagonal Binning: Carat vs Price",
       subtitle = "Alternative to 2D histogram",
       x = "Carat",
       y = "Price (USD)",
       fill = "Count\n(log10)") +
  theme_minimal()

print(p_hex)

Strategy 4: Density Plots

# Density plots
p_density <- ggplot(big_diamonds, aes(x = price, fill = cut)) +
  geom_density(alpha = 0.5) +
  labs(title = "Density Plot of Prices by Cut",
       subtitle = "Shows distribution without individual points",
       x = "Price (USD)",
       y = "Density",
       fill = "Cut") +
  scale_fill_brewer(palette = "Set2") +
  scale_x_continuous(labels = dollar, limits = c(0, 15000)) +
  theme_minimal()

print(p_density)

4.4 Advanced ggplot2 Techniques

Faceting for Multi-dimensional Analysis

# Faceted plots
p_facet <- ggplot(big_diamonds[sample(.N, 5000)], 
                  aes(x = carat, y = price, color = color)) +
  geom_point(alpha = 0.4, size = 1) +
  geom_smooth(method = "lm", se = FALSE, size = 0.5) +
  labs(title = "Carat vs Price by Color and Clarity",
       subtitle = "Faceted visualization",
       x = "Carat",
       y = "Price (USD)") +
  scale_color_brewer(palette = "Spectral") +
  facet_grid(color ~ clarity) +
  theme(legend.position = "none",
        axis.text.x = element_text(angle = 45, hjust = 1))

print(p_facet)

Statistical Transformations

# Using stat_summary for aggregation
p_stat <- ggplot(big_diamonds, aes(x = cut, y = price)) +
  stat_summary(fun = mean, geom = "point", size = 3, color = "red") +
  stat_summary(fun.data = mean_cl_normal, 
               geom = "errorbar", 
               width = 0.2,
               color = "red") +
  stat_summary(fun = median, geom = "point", size = 3, color = "blue") +
  labs(title = "Mean and Median Prices by Cut",
       subtitle = "With 95% confidence intervals for means",
       x = "Cut Quality",
       y = "Price (USD)") +
  scale_y_continuous(labels = dollar) +
  theme_minimal()

print(p_stat)

Combining Multiple Plots with patchwork

library(patchwork)

# Create individual plots
p1 <- ggplot(big_diamonds, aes(x = cut, y = price)) +
  geom_boxplot(fill = "lightblue") +
  labs(title = "Box Plot", x = NULL, y = "Price")

p2 <- ggplot(big_diamonds, aes(x = price, fill = cut)) +
  geom_density(alpha = 0.5) +
  labs(title = "Density Plot", x = "Price", y = "Density") +
  theme(legend.position = "none")

p3 <- ggplot(big_diamonds, aes(x = cut, fill = cut)) +
  geom_bar() +
  labs(title = "Bar Plot", x = "Cut", y = "Count") +
  theme(legend.position = "none")

p4 <- ggplot(big_diamonds[sample(.N, 1000)], 
             aes(x = carat, y = price, color = cut)) +
  geom_point(alpha = 0.5) +
  labs(title = "Scatter Plot", x = "Carat", y = "Price") +
  theme(legend.position = "none")

# Combine plots
(p1 + p2) / (p3 + p4) +
  plot_annotation(title = "Multi-panel Visualization of Diamond Data",
                  theme = theme(plot.title = element_text(hjust = 0.5, size = 14)))

5. Interactive Visualization with plotly

plotly is an interactive visualization library that works seamlessly with ggplot2 and can handle large datasets efficiently.

5.1 Converting ggplot2 to plotly

# Create a ggplot
p_gg <- ggplot(big_diamonds[sample(.N, 2000)], 
               aes(x = carat, y = price, 
                   color = cut, size = depth,
                   text = paste("Cut:", cut, "<br>",
                                "Color:", color, "<br>",
                                "Clarity:", clarity, "<br>",
                                "Price: $", price))) +
  geom_point(alpha = 0.6) +
  scale_color_brewer(palette = "Set2") +
  labs(title = "Interactive Diamond Explorer",
       x = "Carat",
       y = "Price (USD)") +
  theme_minimal()

# Convert to plotly
p_plotly <- ggplotly(p_gg, tooltip = "text") %>%
  layout(hoverlabel = list(bgcolor = "white"),
         title = list(text = "Interactive Diamond Explorer<br><sub>Hover for details</sub>"))

# Display
p_plotly

5.2 Direct plotly Visualizations

Interactive Scatter Plot

# Create interactive scatter plot directly with plotly
direct_plot <- plot_ly(
  data = big_diamonds[sample(.N, 3000)],
  x = ~carat,
  y = ~price,
  color = ~cut,
  colors = "Set2",
  type = "scatter",
  mode = "markers",
  marker = list(size = 8, opacity = 0.6),
  text = ~paste("Cut:", cut, "<br>",
                "Color:", color, "<br>",
                "Clarity:", clarity, "<br>",
                "Carat:", round(carat, 2), "<br>",
                "Price: $", format(price, big.mark = ",")),
  hoverinfo = "text"
) %>%
  layout(
    title = "Interactive Diamond Price Explorer",
    xaxis = list(title = "Carat"),
    yaxis = list(title = "Price (USD)"),
    hoverlabel = list(bgcolor = "white", font = list(size = 12))
  )

direct_plot

3D Scatter Plot

# 3D scatter plot
plot_3d <- plot_ly(
  data = big_diamonds[sample(.N, 2000)],
  x = ~carat,
  y = ~depth,
  z = ~price,
  color = ~cut,
  colors = "Set2",
  type = "scatter3d",
  mode = "markers",
  marker = list(size = 4, opacity = 0.7),
  text = ~paste("Cut:", cut, "<br>Price: $", price),
  hoverinfo = "text"
) %>%
  layout(
    title = "3D Diamond Analysis",
    scene = list(
      xaxis = list(title = "Carat"),
      yaxis = list(title = "Depth (%)"),
      zaxis = list(title = "Price (USD)")
    )
  )

plot_3d

Interactive Histogram

# Interactive histogram with multiple traces
plotly_hist <- plot_ly(alpha = 0.6)

# Add traces for each cut
cuts <- unique(big_diamonds$cut)
colors <- brewer.pal(length(cuts), "Set2")

for (i in seq_along(cuts)) {
  plotly_hist <- plotly_hist %>%
    add_histogram(
      x = big_diamonds[cut == cuts[i]]$price,
      name = cuts[i],
      marker = list(color = colors[i]),
      opacity = 0.6
    )
}

plotly_hist <- plotly_hist %>%
  layout(
    title = "Interactive Price Distribution by Cut",
    xaxis = list(title = "Price (USD)"),
    yaxis = list(title = "Count"),
    barmode = "overlay",
    hovermode = "x unified"
  )

plotly_hist

5.3 Advanced plotly Features

Linked Brushing

# Create subplots with linked brushing
fig1 <- plot_ly(
  data = big_diamonds[sample(.N, 2000)],
  x = ~carat,
  y = ~price,
  color = ~cut,
  type = "scatter",
  mode = "markers",
  source = "A"
)

fig2 <- plot_ly(
  data = big_diamonds[sample(.N, 2000)],
  x = ~depth,
  y = ~table,
  color = ~cut,
  type = "scatter",
  mode = "markers",
  source = "A"
)

linked_plot <- subplot(fig1, fig2, titleX = TRUE, titleY = TRUE) %>%
  layout(
    title = "Linked Brushing: Select points in one plot to highlight in the other",
    showlegend = FALSE
  ) %>%
  highlight(
    on = "plotly_selected",
    off = "plotly_deselect",
    persistent = FALSE
  )

linked_plot

Dashboards with Subplots

# Create a dashboard with multiple plots
library(plotly)

# Plot 1: Scatter
p1 <- plot_ly(big_diamonds[sample(.N, 1000)], 
              x = ~carat, y = ~price, 
              color = ~cut, type = "scatter", mode = "markers",
              marker = list(size = 6, opacity = 0.7)) %>%
  layout(xaxis = list(title = "Carat"),
         yaxis = list(title = "Price"))

# Plot 2: Box plot
p2 <- plot_ly(big_diamonds, 
              x = ~cut, y = ~price, 
              color = ~cut, type = "box") %>%
  layout(xaxis = list(title = "Cut"),
         yaxis = list(title = "Price"),
         showlegend = FALSE)

# Plot 3: Histogram
p3 <- plot_ly(x = big_diamonds$price, 
              type = "histogram",
              marker = list(color = "steelblue")) %>%
  layout(xaxis = list(title = "Price"),
         yaxis = list(title = "Count"))

# Plot 4: Bar chart
cut_counts <- big_diamonds[, .N, by = cut]
p4 <- plot_ly(cut_counts, 
              x = ~cut, y = ~N, 
              type = "bar",
              marker = list(color = brewer.pal(5, "Set3"))) %>%
  layout(xaxis = list(title = "Cut"),
         yaxis = list(title = "Count"))

# Combine into dashboard
dashboard <- subplot(p1, p2, p3, p4, 
                     nrows = 2, 
                     shareX = FALSE, 
                     shareY = FALSE,
                     titleX = TRUE, 
                     titleY = TRUE) %>%
  layout(title = "Diamond Data Dashboard",
         showlegend = TRUE)

dashboard

6. Performance Comparison: Base R vs ggplot2 vs plotly

Let’s compare the performance and features of different visualization approaches.

# Function to time plot creation
time_plot_creation <- function(plot_func, iterations = 10) {
  times <- numeric(iterations)
  for (i in 1:iterations) {
    start_time <- Sys.time()
    plot_func()
    times[i] <- as.numeric(Sys.time() - start_time)
  }
  return(mean(times))
}

# Define plot functions
base_r_plot <- function() {
  par(mfrow = c(1, 1))
  plot(big_diamonds[sample(.N, 5000)]$carat,
       big_diamonds[sample(.N, 5000)]$price,
       main = "Base R Plot",
       xlab = "Carat", ylab = "Price",
       pch = 16, col = alpha("blue", 0.3))
}

ggplot_plot <- function() {
  p <- ggplot(big_diamonds[sample(.N, 5000)], 
              aes(x = carat, y = price)) +
    geom_point(alpha = 0.3, color = "blue") +
    labs(title = "ggplot2 Plot",
         x = "Carat", y = "Price")
  print(p)
}

# Time the plots
set.seed(123)
base_time <- time_plot_creation(base_r_plot, 5)

ggplot_time <- time_plot_creation(ggplot_plot, 5)

# Create comparison data
comparison_data <- data.frame(
  Method = c("Base R", "ggplot2", "plotly (static)", "plotly (interactive)"),
  Speed = c(base_time, ggplot_time, ggplot_time * 1.5, ggplot_time * 2),
  Interactivity = c("None", "None", "High", "High"),
  Aesthetics = c("Basic", "Excellent", "Excellent", "Excellent"),
  Learning_Curve = c("Low", "Medium", "Medium", "Medium"),
  Best_For = c("Quick exploration", "Publication plots", 
               "Web dashboards", "Interactive reports")
)

# Display comparison table
DT::datatable(comparison_data,
              options = list(pageLength = 10, 
                             dom = 't',
                             columnDefs = list(
                               list(className = 'dt-center', targets = 1:5)
                             )),
              rownames = FALSE,
              caption = "Visualization Method Comparison")

Visual Feature Comparison

# Create a visualization of feature comparison
features <- data.frame(
  Feature = rep(c("Speed", "Customization", "Interactivity", 
                  "3D Support", "Animation", "Export Quality"), 3),
  Score = c(9, 6, 2, 3, 1, 7,   # Base R
            7, 9, 3, 4, 4, 9,   # ggplot2
            5, 8, 10, 9, 9, 8), # plotly
  Method = rep(c("Base R", "ggplot2", "plotly"), each = 6)
)

p_features <- ggplot(features, aes(x = Feature, y = Score, fill = Method)) +
  geom_bar(stat = "identity", position = "dodge", width = 0.7) +
  geom_text(aes(label = Score), 
            position = position_dodge(width = 0.7),
            vjust = -0.5, size = 3) +
  scale_fill_brewer(palette = "Set2") +
  labs(title = "Visualization Method Feature Comparison",
       subtitle = "Score out of 10 for each feature",
       x = NULL,
       y = "Score",
       fill = "Method") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

print(p_features)

7. Best Practices for Big Data Visualization

7.1 When to Use Each Method

Base R Graphics: - Quick exploratory data analysis - Simple diagnostic plots - When working in minimal environments - For very basic, no-frills visualizations

ggplot2: - Publication-quality static graphics - Complex multi-layered visualizations - When consistency across plots is important - For detailed customization and theming

plotly: - Interactive web applications and dashboards - When users need to explore data dynamically - For 3D visualizations - When sharing visualizations online

7.2 Optimization Tips

For ggplot2:

# 1. Use sampling for large datasets
ggplot(big_diamonds[sample(.N, 10000)], aes(x, y)) + geom_point()

# 2. Use binning for dense data
ggplot(big_diamonds, aes(x, y)) + geom_bin2d(bins = 100)

# 3. Use density plots instead of scatter plots
ggplot(big_diamonds, aes(x, fill = group)) + geom_density(alpha = 0.5)

# 4. Avoid overplotting with transparency
ggplot(data, aes(x, y)) + geom_point(alpha = 0.1)

# 5. Use efficient geometries
# geom_hex() is often faster than geom_point() for large data

For plotly:

# 1. Limit data points for scatter plots
plot_ly(data[sample(.N, 10000)], x = ~x, y = ~y)

# 2. Use WebGL for very large datasets
plot_ly(data, x = ~x, y = ~y, type = 'scattergl')

# 3. Aggregate data before plotting
aggregated <- data[, .(mean_y = mean(y)), by = x]
plot_ly(aggregated, x = ~x, y = ~mean_y)

# 4. Use server-side processing for massive datasets
# Consider shiny or dash applications

7.3 Memory Management

# Function to monitor memory during visualization
monitor_viz_memory <- function(viz_func, func_name) {
  mem_before <- pryr::mem_used()
  viz_func()
  mem_after <- pryr::mem_used()
  
  return(data.frame(
    Method = func_name,
    Memory_Used_MB = round((mem_after - mem_before) / 1024^2, 2),
    Memory_Used_GB = round((mem_after - mem_before) / 1024^3, 3)
  ))
}

# Test memory usage
memory_results <- rbind(
  monitor_viz_memory(base_r_plot, "Base R"),
  monitor_viz_memory(ggplot_plot, "ggplot2")
)

print(memory_results)
##    Method Memory_Used_MB Memory_Used_GB
## 1  Base R           0.07          0.000
## 2 ggplot2           1.24          0.001

Practical Exercises

Exercise 1: Diamond Price Analysis

Create a comprehensive visualization dashboard that includes:

  1. A scatter plot showing carat vs price, colored by cut, with smoothed trend lines
  2. A set of faceted histograms showing price distribution for each clarity grade
  3. An interactive plotly visualization that allows users to select a price range and see the corresponding diamonds
  4. A summary table showing average price by cut and color combination

Exercise 2: Performance Optimization

Take a large dataset (or simulate one with 1 million rows) and:

  1. Create three versions of the same scatter plot using:
    • All data points with transparency
    • 1% random sampling
    • Hexagonal binning
  2. Compare the creation time and memory usage of each approach
  3. Create an interactive version using plotly with a slider to adjust the sample size

Exercise 3: Interactive Dashboard

Using the diamonds dataset, create an interactive dashboard with:

  1. A main scatter plot of carat vs price
  2. Controls to filter by cut, color, and clarity
  3. A histogram showing the distribution of the selected data
  4. A summary statistics panel
  5. Export functionality for the filtered data

Key Takeaways

  1. Choose the right tool for the job: Base R for quick looks, ggplot2 for publication graphics, plotly for interactivity
  2. Handle large datasets strategically: Use sampling, binning, and aggregation to manage performance
  3. Consider your audience: Static plots for reports, interactive plots for exploration
  4. Optimize for performance: Monitor memory usage and rendering times
  5. Iterate and refine: Start simple, then add complexity as needed

Next Steps

  1. Practice with your own datasets
  2. Explore advanced ggplot2 extensions (ggrepel, gganimate, ggforce)
  3. Learn to create dashboards with shiny + plotly
  4. Study color theory and accessibility for better visualizations
  5. Join the ggplot2 and plotly communities for ongoing learning

Additional Resources


This material is part of the training program by The National Centre for Research Methods © NCRM authored by Dr Somnath Chaudhuri (University of Southampton). Content is under a CC BY‑style permissive license and can be freely used for educational purposes with proper attribution.

LS0tDQp0aXRsZTogIlZpc3VhbGl6YXRpb24gaW4gQmlnIERhdGEgQ29udGV4dCINCmF1dGhvcjogIlNvbW5hdGggQ2hhdWRodXJpLCBVbml2ZXJzaXR5IG9mIFNvdXRoYW1wdG9uLCBVSyINCmRhdGU6ICJgciBmb3JtYXQoU3lzLkRhdGUoKSwgJyVCICVkLCAlWScpYCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZGVwdGg6IDMNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICB0aGVtZTogY29zbW8NCiAgICBoaWdobGlnaHQ6IHRhbmdvDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2RlcHRoOiAzDQpnZW9tZXRyeTogbWFyZ2luPTFpbg0KZm9udHNpemU6IDExcHQNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldCgNCiAgZWNobyA9IFRSVUUsDQogIHdhcm5pbmcgPSBGQUxTRSwNCiAgbWVzc2FnZSA9IEZBTFNFLA0KICBmaWcud2lkdGggPSAxMCwNCiAgZmlnLmhlaWdodCA9IDYsDQogIGZpZy5hbGlnbiA9ICdjZW50ZXInLA0KICBjYWNoZSA9IFRSVUUNCikNCg0KIyBMb2FkIHJlcXVpcmVkIGxpYnJhcmllcw0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KGRhdGEudGFibGUpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShwYXRjaHdvcmspDQpsaWJyYXJ5KHZpcmlkaXMpDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCmxpYnJhcnkoc2NhbGVzKQ0KbGlicmFyeShEVCkNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkoZ2FwbWluZGVyKQ0KDQojIFNldCBnZ3Bsb3QgdGhlbWUNCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDEyKSArDQogICAgICAgICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiksDQogICAgICAgICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksDQogICAgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpKQ0KDQojIENyZWF0ZSBkaXJlY3Rvcmllcw0KaWYgKCFkaXIuZXhpc3RzKCJkYXRhIikpIGRpci5jcmVhdGUoImRhdGEiKQ0KaWYgKCFkaXIuZXhpc3RzKCJmaWd1cmVzIikpIGRpci5jcmVhdGUoImZpZ3VyZXMiKQ0KaWYgKCFkaXIuZXhpc3RzKCJvdXRwdXQiKSkgZGlyLmNyZWF0ZSgib3V0cHV0IikNCmBgYA0KDQojIFZpc3VhbGl6YXRpb24gaW4gQmlnIERhdGEgQ29udGV4dA0KDQojIyBMZWFybmluZyBPYmplY3RpdmVzDQoNCkJ5IHRoZSBlbmQgb2YgdGhpcyBzZWN0aW9uLCB5b3Ugd2lsbCBiZSBhYmxlIHRvOg0KDQoxLiBVbmRlcnN0YW5kIHRoZSBjaGFsbGVuZ2VzIG9mIHZpc3VhbGl6aW5nIGJpZyBkYXRhDQoyLiBDcmVhdGUgZWZmZWN0aXZlIHZpc3VhbGl6YXRpb25zIHdpdGggZ2dwbG90MiBmb3IgbGFyZ2UgZGF0YXNldHMNCjMuIEJ1aWxkIGludGVyYWN0aXZlIHZpc3VhbGl6YXRpb25zIHVzaW5nIHBsb3RseQ0KNC4gSW1wbGVtZW50IHNhbXBsaW5nIGFuZCBhZ2dyZWdhdGlvbiBzdHJhdGVnaWVzIGZvciBiaWcgZGF0YSB2aXN1YWxpemF0aW9uDQo1LiBDb21wYXJlIGRpZmZlcmVudCB2aXN1YWxpemF0aW9uIGFwcHJvYWNoZXMgYW5kIHRvb2xzDQoNCiMjIEludHJvZHVjdGlvbg0KDQpWaXN1YWxpemF0aW9uIGlzIGEgY3JpdGljYWwgY29tcG9uZW50IG9mIGRhdGEgYW5hbHlzaXMsIGVzcGVjaWFsbHkgaW4gdGhlIGNvbnRleHQgb2YgYmlnIGRhdGEuIFdoaWxlIHZpc3VhbGl6YXRpb24gaGVscHMgdW5jb3ZlciBwYXR0ZXJucyBhbmQgaW5zaWdodHMsIHdvcmtpbmcgd2l0aCBsYXJnZSBkYXRhc2V0cyBwcmVzZW50cyB1bmlxdWUgY2hhbGxlbmdlcyBzdWNoIGFzIG92ZXJwbG90dGluZywgcGVyZm9ybWFuY2UgaXNzdWVzLCBhbmQgaW5mb3JtYXRpb24gb3ZlcmxvYWQuIEluIHRoaXMgc2VjdGlvbiwgd2UnbGwgZXhwbG9yZSBzdHJhdGVnaWVzIGFuZCB0b29scyB0byBlZmZlY3RpdmVseSB2aXN1YWxpemUgbGFyZ2UgZGF0YXNldHMgdXNpbmcgUi4NCg0KIyAxLiBDaGFsbGVuZ2VzIG9mIFZpc3VhbGl6aW5nIEJpZyBEYXRhDQoNClZpc3VhbGl6aW5nIGJpZyBkYXRhIGNvbWVzIHdpdGggc2V2ZXJhbCBjaGFsbGVuZ2VzIHRoYXQgcmVxdWlyZSBzcGVjaWFsIGNvbnNpZGVyYXRpb246DQoNCiMjIyAxLjEgT3ZlcnBsb3R0aW5nDQpXaGVuIHRvbyBtYW55IHBvaW50cyBvdmVybGFwIGluIGEgc2NhdHRlciBwbG90LCBwYXR0ZXJucyBiZWNvbWUgb2JzY3VyZWQuIFRoaXMgaXMgcGFydGljdWxhcmx5IHByb2JsZW1hdGljIHdpdGggZGF0YXNldHMgY29udGFpbmluZyBtaWxsaW9ucyBvZiBvYnNlcnZhdGlvbnMuDQoNCiMjIyAxLjIgUGVyZm9ybWFuY2UgSXNzdWVzDQpSZW5kZXJpbmcgbWlsbGlvbnMgb2YgcG9pbnRzIGNhbiBiZSBjb21wdXRhdGlvbmFsbHkgaW50ZW5zaXZlIGFuZCBzbG93LCBib3RoIGluIHRlcm1zIG9mIGdlbmVyYXRpb24gYW5kIGludGVyYWN0aXZpdHkuDQoNCiMjIyAxLjMgTWVtb3J5IENvbnN0cmFpbnRzDQpMYXJnZSBkYXRhc2V0cyBtYXkgZXhjZWVkIGF2YWlsYWJsZSBtZW1vcnkgd2hlbiBjcmVhdGluZyBjb21wbGV4IHZpc3VhbGl6YXRpb25zLg0KDQojIyMgMS40IEluZm9ybWF0aW9uIE92ZXJsb2FkDQpUb28gbXVjaCBpbmZvcm1hdGlvbiBpbiBhIHNpbmdsZSB2aXN1YWxpemF0aW9uIGNhbiBtYWtlIGl0IGRpZmZpY3VsdCB0byBleHRyYWN0IG1lYW5pbmdmdWwgaW5zaWdodHMuDQoNCiMjIyAxLjUgU2NhbGFiaWxpdHkNClZpc3VhbGl6YXRpb25zIG5lZWQgdG8gcmVtYWluIGVmZmVjdGl2ZSBhbmQgcmVhZGFibGUgYXQgZGlmZmVyZW50IGRhdGEgc2NhbGVzLg0KDQojIyBTdHJhdGVnaWVzIGZvciBCaWcgRGF0YSBWaXN1YWxpemF0aW9uDQoNClRvIGFkZHJlc3MgdGhlc2UgY2hhbGxlbmdlcywgd2UgZW1wbG95IHNldmVyYWwgc3RyYXRlZ2llczoNCg0KMS4gKipTYW1wbGluZyoqOiBVc2UgcmVwcmVzZW50YXRpdmUgc3Vic2V0cyBvZiBkYXRhDQoyLiAqKkFnZ3JlZ2F0aW9uKio6IFN1bW1hcml6ZSBkYXRhIGJlZm9yZSB2aXN1YWxpemF0aW9uDQozLiAqKkJpbm5pbmcqKjogR3JvdXAgZGF0YSBwb2ludHMgaW50byBiaW5zIG9yIGhleGFnb25zDQo0LiAqKkRlbnNpdHkgRXN0aW1hdGlvbioqOiBTaG93IGRhdGEgZGVuc2l0eSByYXRoZXIgdGhhbiBpbmRpdmlkdWFsIHBvaW50cw0KNS4gKipJbnRlcmFjdGl2ZSBFeHBsb3JhdGlvbioqOiBBbGxvdyB1c2VycyB0byBkcmlsbCBkb3duIGludG8gZGV0YWlscw0KNi4gKipQcm9ncmVzc2l2ZSBSZW5kZXJpbmcqKjogU2hvdyBkYXRhIGluIHN0YWdlcw0KDQojIDIuIExvYWRpbmcgYW5kIFByZXBhcmluZyB0aGUgRGF0YXNldA0KDQpGb3IgdGhpcyB0cmFpbmluZywgd2UnbGwgdXNlIHRoZSAqKkRpYW1vbmRzIERhdGFzZXQqKiB3aGljaCBjb250YWlucyA1Myw5NDAgb2JzZXJ2YXRpb25zIG9mIGRpYW1vbmQgY2hhcmFjdGVyaXN0aWNzLiBXaGlsZSBub3QgImJpZyBkYXRhIiBieSBtb2Rlcm4gc3RhbmRhcmRzLCBpdCdzIGxhcmdlIGVub3VnaCB0byBkZW1vbnN0cmF0ZSB2aXN1YWxpemF0aW9uIGNoYWxsZW5nZXMgYW5kIHRlY2huaXF1ZXMuDQoNCmBgYHtyIGxvYWQtZGF0YX0NCiMgTG9hZCB0aGUgZGlhbW9uZHMgZGF0YXNldCAoY29tZXMgd2l0aCBnZ3Bsb3QyKQ0KZGF0YShkaWFtb25kcykNCg0KIyBDb252ZXJ0IHRvIGRhdGEudGFibGUgZm9yIGVmZmljaWVudCBtYW5pcHVsYXRpb24NCmRpYW1vbmRzX2R0IDwtIGFzLmRhdGEudGFibGUoZGlhbW9uZHMpDQoNCiMgQ3JlYXRlIGEgbGFyZ2VyIHZlcnNpb24gYnkgc2FtcGxpbmcgd2l0aCByZXBsYWNlbWVudCAoZm9yIGRlbW9uc3RyYXRpb24pDQpzZXQuc2VlZCgxMjMpDQpiaWdfZGlhbW9uZHMgPC0gZGlhbW9uZHNfZHRbc2FtcGxlKC5OLCAxMDAwMDAsIHJlcGxhY2UgPSBUUlVFKV0NCg0KIyBBZGQgc29tZSBkZXJpdmVkIGNvbHVtbnMNCmJpZ19kaWFtb25kc1ssIHByaWNlX3Blcl9jYXJhdCA6PSBwcmljZSAvIGNhcmF0XQ0KYmlnX2RpYW1vbmRzWywgbG9nX3ByaWNlIDo9IGxvZzEwKHByaWNlKV0NCmJpZ19kaWFtb25kc1ssIHNpemVfY2F0ZWdvcnkgOj0gY3V0KGNhcmF0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBjKDAsIDAuNSwgMSwgMS41LCAyLCA1KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJUaW55IiwgIlNtYWxsIiwgIk1lZGl1bSIsICJMYXJnZSIsICJWZXJ5IExhcmdlIikpXQ0KDQojIFNhdmUgdG8gZmlsZSBmb3IgY29uc2lzdGVuY3kNCmZ3cml0ZShiaWdfZGlhbW9uZHMsICJkYXRhL2JpZ19kaWFtb25kcy5jc3YiKQ0KDQojIERpc3BsYXkgZGF0YXNldCBpbmZvcm1hdGlvbg0KY2F0KCJEYXRhc2V0IEluZm9ybWF0aW9uOlxuIikNCmNhdCgiTnVtYmVyIG9mIHJvd3M6IiwgZm9ybWF0KG5yb3coYmlnX2RpYW1vbmRzKSwgYmlnLm1hcmsgPSAiLCIpLCAiXG4iKQ0KY2F0KCJOdW1iZXIgb2YgY29sdW1uczoiLCBuY29sKGJpZ19kaWFtb25kcyksICJcblxuIikNCg0KY2F0KCJGaXJzdCBmZXcgcm93czpcbiIpDQpEVDo6ZGF0YXRhYmxlKGJpZ19kaWFtb25kc1sxOjEwLCBdLCANCiAgICAgICAgICAgICAgb3B0aW9ucyA9IGxpc3QocGFnZUxlbmd0aCA9IDEwLCBzY3JvbGxYID0gVFJVRSkpDQpgYGANCg0KIyMgRGF0YXNldCBTdHJ1Y3R1cmUNCg0KYGBge3IgZGF0YXNldC1zdHJ1Y3R1cmV9DQojIERpc3BsYXkgc3RydWN0dXJlDQpjYXQoIkRhdGFzZXQgU3RydWN0dXJlOlxuIikNCnN0cihiaWdfZGlhbW9uZHNbLCAxOjhdKQ0KDQojIFN1bW1hcnkgc3RhdGlzdGljcw0KY2F0KCJcblxuU3VtbWFyeSBTdGF0aXN0aWNzOlxuIikNCnN1bW1hcnlfc3RhdHMgPC0gYmlnX2RpYW1vbmRzWywgLigNCiAgT2JzZXJ2YXRpb25zID0gLk4sDQogIFVuaXF1ZV9DdXRzID0gbGVuZ3RoKHVuaXF1ZShjdXQpKSwNCiAgVW5pcXVlX0NvbG9ycyA9IGxlbmd0aCh1bmlxdWUoY29sb3IpKSwNCiAgVW5pcXVlX0NsYXJpdHkgPSBsZW5ndGgodW5pcXVlKGNsYXJpdHkpKSwNCiAgQXZnX1ByaWNlID0gbWVhbihwcmljZSksDQogIEF2Z19DYXJhdCA9IG1lYW4oY2FyYXQpLA0KICBNYXhfUHJpY2UgPSBtYXgocHJpY2UpLA0KICBNaW5fUHJpY2UgPSBtaW4ocHJpY2UpDQopXQ0KDQpwcmludChzdW1tYXJ5X3N0YXRzKQ0KYGBgDQoNCiMgMy4gQmFzZSBSIEdyYXBoaWNzIGZvciBRdWljayBFeHBsb3JhdGlvbg0KDQpCZWZvcmUgZGl2aW5nIGludG8gZ2dwbG90MiwgbGV0J3MgcmV2aWV3IGJhc2UgUiBncmFwaGljcyB3aGljaCBjYW4gYmUgdXNlZnVsIGZvciBxdWljayBleHBsb3JhdG9yeSBhbmFseXNpcy4NCg0KYGBge3IgYmFzZS1yLWdyYXBoaWNzLCBmaWcuaGVpZ2h0PTh9DQojIFNldCB1cCBtdWx0aS1wYW5lbCBwbG90DQpwYXIobWZyb3cgPSBjKDIsIDIpLCBtYXIgPSBjKDQsIDQsIDIsIDEpKQ0KDQojIDEuIEhpc3RvZ3JhbSBvZiBwcmljZQ0KaGlzdChiaWdfZGlhbW9uZHMkcHJpY2UsIGJyZWFrcyA9IDUwLCANCiAgICAgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgRGlhbW9uZCBQcmljZXMiLA0KICAgICB4bGFiID0gIlByaWNlIChVU0QpIiwgDQogICAgIHlsYWIgPSAiRnJlcXVlbmN5IiwNCiAgICAgY29sID0gInN0ZWVsYmx1ZSIsDQogICAgIGJvcmRlciA9ICJ3aGl0ZSIpDQoNCiMgMi4gQm94cGxvdCBvZiBwcmljZSBieSBjdXQNCmJveHBsb3QocHJpY2UgfiBjdXQsIGRhdGEgPSBiaWdfZGlhbW9uZHMsDQogICAgICAgIG1haW4gPSAiUHJpY2UgYnkgRGlhbW9uZCBDdXQiLA0KICAgICAgICB4bGFiID0gIkN1dCBRdWFsaXR5IiwNCiAgICAgICAgeWxhYiA9ICJQcmljZSAoVVNEKSIsDQogICAgICAgIGNvbCA9IGJyZXdlci5wYWwoNSwgIlNldDIiKSwNCiAgICAgICAgb3V0bGluZSA9IEZBTFNFKQ0KDQojIDMuIFNjYXR0ZXIgcGxvdCAoc2FtcGxlZCBmb3IgY2xhcml0eSkNCnNhbXBsZV9pbmRpY2VzIDwtIHNhbXBsZShucm93KGJpZ19kaWFtb25kcyksIDEwMDApDQpwbG90KGJpZ19kaWFtb25kcyRjYXJhdFtzYW1wbGVfaW5kaWNlc10sIA0KICAgICBiaWdfZGlhbW9uZHMkcHJpY2Vbc2FtcGxlX2luZGljZXNdLA0KICAgICBtYWluID0gIkNhcmF0IHZzIFByaWNlICgxLDAwMCBwb2ludHMpIiwNCiAgICAgeGxhYiA9ICJDYXJhdCIsDQogICAgIHlsYWIgPSAiUHJpY2UgKFVTRCkiLA0KICAgICBwY2ggPSAxNiwNCiAgICAgY29sID0gYWxwaGEoImRhcmtyZWQiLCAwLjMpLA0KICAgICBjZXggPSAwLjgpDQoNCiMgNC4gQmFyIHBsb3Qgb2YgY3V0cw0KY3V0X2NvdW50cyA8LSB0YWJsZShiaWdfZGlhbW9uZHMkY3V0KQ0KYmFycGxvdChjdXRfY291bnRzLA0KICAgICAgICBtYWluID0gIkRpc3RyaWJ1dGlvbiBvZiBEaWFtb25kIEN1dHMiLA0KICAgICAgICB4bGFiID0gIkN1dCIsDQogICAgICAgIHlsYWIgPSAiQ291bnQiLA0KICAgICAgICBjb2wgPSBicmV3ZXIucGFsKDUsICJTZXQzIiksDQogICAgICAgIGJvcmRlciA9IE5BKQ0KDQojIFJlc2V0IHBhcg0KcGFyKG1mcm93ID0gYygxLCAxKSkNCmBgYA0KDQojIDQuIEludHJvZHVjdGlvbiB0byBnZ3Bsb3QyDQoNCmdncGxvdDIgaXMgYSBwb3dlcmZ1bCB2aXN1YWxpemF0aW9uIHBhY2thZ2UgYmFzZWQgb24gdGhlIEdyYW1tYXIgb2YgR3JhcGhpY3MuIEl0IHByb3ZpZGVzIGEgY29uc2lzdGVudCBhbmQgZmxleGlibGUgZnJhbWV3b3JrIGZvciBjcmVhdGluZyBjb21wbGV4IHZpc3VhbGl6YXRpb25zLg0KDQojIyA0LjEgQmFzaWMgZ2dwbG90MiBDb21wb25lbnRzDQoNCkV2ZXJ5IGdncGxvdDIgdmlzdWFsaXphdGlvbiBjb25zaXN0cyBvZjoNCg0KMS4gKipEYXRhKio6IFRoZSBkYXRhc2V0IGJlaW5nIHZpc3VhbGl6ZWQNCjIuICoqQWVzdGhldGljcyAoYWVzKSoqOiBNYXBwaW5ncyBmcm9tIGRhdGEgdG8gdmlzdWFsIHByb3BlcnRpZXMNCjMuICoqR2VvbWV0cmllcyAoZ2VvbSkqKjogVGhlIHR5cGUgb2YgcGxvdCAocG9pbnRzLCBsaW5lcywgYmFycywgZXRjLikNCjQuICoqU2NhbGVzKio6IENvbnRyb2wgb2YgYWVzdGhldGljIG1hcHBpbmdzDQo1LiAqKkZhY2V0cyoqOiBDcmVhdGUgbXVsdGlwbGUgcGxvdHMgYmFzZWQgb24gZGF0YSBzdWJzZXRzDQo2LiAqKlRoZW1lcyoqOiBDb250cm9sIG5vbi1kYXRhIGVsZW1lbnRzIChiYWNrZ3JvdW5kLCBncmlkcywgZXRjLikNCg0KIyMgNC4yIEJhc2ljIFBsb3QgVHlwZXMNCg0KIyMjIFNjYXR0ZXIgUGxvdA0KDQpgYGB7ciBzY2F0dGVyLXBsb3QtYmFzaWN9DQojIEJhc2ljIHNjYXR0ZXIgcGxvdA0KcF9zY2F0dGVyIDwtIGdncGxvdChiaWdfZGlhbW9uZHNbc2FtcGxlKC5OLCA1MDAwKV0sICAjIFNhbXBsZSBmb3IgY2xhcml0eQ0KICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGNvbG9yID0gY3V0KSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC42LCBzaXplID0gMS41KSArDQogIGxhYnModGl0bGUgPSAiRGlhbW9uZCBQcmljZSB2cyBDYXJhdCBieSBDdXQiLA0KICAgICAgIHN1YnRpdGxlID0gIlNhbXBsZSBvZiA1LDAwMCBkaWFtb25kcyIsDQogICAgICAgeCA9ICJDYXJhdCIsDQogICAgICAgeSA9ICJQcmljZSAoVVNEKSIsDQogICAgICAgY29sb3IgPSAiQ3V0IFF1YWxpdHkiKSArDQogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDIiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpwcmludChwX3NjYXR0ZXIpDQpgYGANCg0KIyMjIEhpc3RvZ3JhbQ0KDQpgYGB7ciBoaXN0b2dyYW0tYmFzaWN9DQojIEhpc3RvZ3JhbSB3aXRoIGRlbnNpdHkgb3ZlcmxheQ0KcF9oaXN0IDwtIGdncGxvdChiaWdfZGlhbW9uZHMsIGFlcyh4ID0gcHJpY2UpKSArDQogIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gLi5kZW5zaXR5Li4pLA0KICAgICAgICAgICAgICAgICBiaW5zID0gNTAsDQogICAgICAgICAgICAgICAgIGZpbGwgPSAic3RlZWxibHVlIiwNCiAgICAgICAgICAgICAgICAgYWxwaGEgPSAwLjcpICsNCiAgZ2VvbV9kZW5zaXR5KGNvbG9yID0gImRhcmtyZWQiLCBzaXplID0gMSkgKw0KICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBEaWFtb25kIFByaWNlcyIsDQogICAgICAgc3VidGl0bGUgPSAiV2l0aCBkZW5zaXR5IGN1cnZlIG92ZXJsYXkiLA0KICAgICAgIHggPSAiUHJpY2UgKFVTRCkiLA0KICAgICAgIHkgPSAiRGVuc2l0eSIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IGRvbGxhcikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KcHJpbnQocF9oaXN0KQ0KYGBgDQoNCiMjIyBCb3ggUGxvdA0KDQpgYGB7ciBib3hwbG90LWJhc2ljfQ0KIyBCb3ggcGxvdCB3aXRoIGppdHRlcg0KcF9ib3ggPC0gZ2dwbG90KGJpZ19kaWFtb25kcywgYWVzKHggPSBjdXQsIHkgPSBwcmljZSwgZmlsbCA9IGN1dCkpICsNCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuYWxwaGEgPSAwLjMsIG91dGxpZXIuc2l6ZSA9IDEpICsNCiAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjA1LCB3aWR0aCA9IDAuMiwgc2l6ZSA9IDAuNSkgKw0KICBsYWJzKHRpdGxlID0gIlByaWNlIERpc3RyaWJ1dGlvbiBieSBEaWFtb25kIEN1dCIsDQogICAgICAgeCA9ICJDdXQgUXVhbGl0eSIsDQogICAgICAgeSA9ICJQcmljZSAoVVNEKSIpICsNCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJTZXQyIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gZG9sbGFyKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCg0KcHJpbnQocF9ib3gpDQpgYGANCg0KIyMjIEJhciBQbG90DQoNCmBgYHtyIGJhcnBsb3QtYmFzaWN9DQojIEJhciBwbG90DQpwX2JhciA8LSBnZ3Bsb3QoYmlnX2RpYW1vbmRzLCBhZXMoeCA9IGN1dCwgZmlsbCA9IGN1dCkpICsNCiAgZ2VvbV9iYXIoKSArDQogIGxhYnModGl0bGUgPSAiQ291bnQgb2YgRGlhbW9uZHMgYnkgQ3V0IFF1YWxpdHkiLA0KICAgICAgIHggPSAiQ3V0IFF1YWxpdHkiLA0KICAgICAgIHkgPSAiQ291bnQiKSArDQogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiU2V0MyIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogIGNvb3JkX2ZsaXAoKQ0KDQpwcmludChwX2JhcikNCmBgYA0KDQojIyA0LjMgSGFuZGxpbmcgTGFyZ2UgRGF0YXNldHMgd2l0aCBnZ3Bsb3QyDQoNCiMjIyBTdHJhdGVneSAxOiBTYW1wbGluZw0KDQpgYGB7ciBzYW1wbGluZy1zdHJhdGVneX0NCiMgQ29tcGFyZSBkaWZmZXJlbnQgc2FtcGxpbmcgYXBwcm9hY2hlcw0Kc2V0LnNlZWQoMTIzKQ0Kc2FtcGxlcyA8LSBsaXN0KA0KICByYW5kb21fMWsgPSBiaWdfZGlhbW9uZHNbc2FtcGxlKC5OLCAxMDAwKV0sDQogIHJhbmRvbV81ayA9IGJpZ19kaWFtb25kc1tzYW1wbGUoLk4sIDUwMDApXSwNCiAgc3RyYXRpZmllZCA9IGJpZ19kaWFtb25kc1ssIC5TRFtzYW1wbGUoLk4sIG1pbigyMDAsIC5OKSldLCBieSA9IGN1dF0NCikNCg0KIyBDcmVhdGUgY29tcGFyaXNvbiBwbG90cw0KcF9zYW1wbGluZ19jb21wYXJpc29uIDwtIGdncGxvdChzYW1wbGVzJHJhbmRvbV81aywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjdXQpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjMsIHNpemUgPSAxKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UsIHNpemUgPSAwLjUpICsNCiAgbGFicyh0aXRsZSA9ICJTYW1wbGluZyBTdHJhdGVneTogNSwwMDAgUmFuZG9tIFBvaW50cyIsDQogICAgICAgc3VidGl0bGUgPSAiV2l0aCBsaW5lYXIgdHJlbmQgbGluZXMgYnkgY3V0IiwNCiAgICAgICB4ID0gIkNhcmF0IiwNCiAgICAgICB5ID0gIlByaWNlIChVU0QpIikgKw0KICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQyIikgKw0KICBmYWNldF93cmFwKH5jdXQsIG5yb3cgPSAxKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCg0KcHJpbnQocF9zYW1wbGluZ19jb21wYXJpc29uKQ0KYGBgDQoNCiMjIyBTdHJhdGVneSAyOiBCaW5uaW5nICgyRCBIaXN0b2dyYW0pDQoNCmBgYHtyIGJpbm5pbmctc3RyYXRlZ3l9DQojIDJEIGhpc3RvZ3JhbSBmb3IgZGVuc2UgZGF0YQ0KcF9iaW4yZCA8LSBnZ3Bsb3QoYmlnX2RpYW1vbmRzLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UpKSArDQogIGdlb21fYmluMmQoYmlucyA9IDUwKSArDQogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAicGxhc21hIiwgdHJhbnMgPSAibG9nMTAiKSArDQogIGxhYnModGl0bGUgPSAiMkQgSGlzdG9ncmFtOiBDYXJhdCB2cyBQcmljZSIsDQogICAgICAgc3VidGl0bGUgPSAiVXNpbmcgYmlubmluZyB0byBoYW5kbGUgZGVuc2UgZGF0YSIsDQogICAgICAgeCA9ICJDYXJhdCIsDQogICAgICAgeSA9ICJQcmljZSAoVVNEKSIsDQogICAgICAgZmlsbCA9ICJDb3VudFxuKGxvZzEwKSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCnByaW50KHBfYmluMmQpDQpgYGANCg0KIyMjIFN0cmF0ZWd5IDM6IEhleGFnb25hbCBCaW5uaW5nDQoNCmBgYHtyIGhleGJpbi1zdHJhdGVneX0NCiMgSGV4YWdvbmFsIGJpbm5pbmcNCnBfaGV4IDwtIGdncGxvdChiaWdfZGlhbW9uZHMsIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSkpICsNCiAgZ2VvbV9oZXgoYmlucyA9IDQwKSArDQogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiLCB0cmFucyA9ICJsb2cxMCIpICsNCiAgbGFicyh0aXRsZSA9ICJIZXhhZ29uYWwgQmlubmluZzogQ2FyYXQgdnMgUHJpY2UiLA0KICAgICAgIHN1YnRpdGxlID0gIkFsdGVybmF0aXZlIHRvIDJEIGhpc3RvZ3JhbSIsDQogICAgICAgeCA9ICJDYXJhdCIsDQogICAgICAgeSA9ICJQcmljZSAoVVNEKSIsDQogICAgICAgZmlsbCA9ICJDb3VudFxuKGxvZzEwKSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCnByaW50KHBfaGV4KQ0KYGBgDQoNCiMjIyBTdHJhdGVneSA0OiBEZW5zaXR5IFBsb3RzDQoNCmBgYHtyIGRlbnNpdHktc3RyYXRlZ3l9DQojIERlbnNpdHkgcGxvdHMNCnBfZGVuc2l0eSA8LSBnZ3Bsb3QoYmlnX2RpYW1vbmRzLCBhZXMoeCA9IHByaWNlLCBmaWxsID0gY3V0KSkgKw0KICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjUpICsNCiAgbGFicyh0aXRsZSA9ICJEZW5zaXR5IFBsb3Qgb2YgUHJpY2VzIGJ5IEN1dCIsDQogICAgICAgc3VidGl0bGUgPSAiU2hvd3MgZGlzdHJpYnV0aW9uIHdpdGhvdXQgaW5kaXZpZHVhbCBwb2ludHMiLA0KICAgICAgIHggPSAiUHJpY2UgKFVTRCkiLA0KICAgICAgIHkgPSAiRGVuc2l0eSIsDQogICAgICAgZmlsbCA9ICJDdXQiKSArDQogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiU2V0MiIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IGRvbGxhciwgbGltaXRzID0gYygwLCAxNTAwMCkpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCnByaW50KHBfZGVuc2l0eSkNCmBgYA0KDQojIyA0LjQgQWR2YW5jZWQgZ2dwbG90MiBUZWNobmlxdWVzDQoNCiMjIyBGYWNldGluZyBmb3IgTXVsdGktZGltZW5zaW9uYWwgQW5hbHlzaXMNCg0KYGBge3IgZmFjZXRpbmd9DQojIEZhY2V0ZWQgcGxvdHMNCnBfZmFjZXQgPC0gZ2dwbG90KGJpZ19kaWFtb25kc1tzYW1wbGUoLk4sIDUwMDApXSwgDQogICAgICAgICAgICAgICAgICBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UsIGNvbG9yID0gY29sb3IpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjQsIHNpemUgPSAxKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UsIHNpemUgPSAwLjUpICsNCiAgbGFicyh0aXRsZSA9ICJDYXJhdCB2cyBQcmljZSBieSBDb2xvciBhbmQgQ2xhcml0eSIsDQogICAgICAgc3VidGl0bGUgPSAiRmFjZXRlZCB2aXN1YWxpemF0aW9uIiwNCiAgICAgICB4ID0gIkNhcmF0IiwNCiAgICAgICB5ID0gIlByaWNlIChVU0QpIikgKw0KICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTcGVjdHJhbCIpICsNCiAgZmFjZXRfZ3JpZChjb2xvciB+IGNsYXJpdHkpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQpwcmludChwX2ZhY2V0KQ0KYGBgDQoNCiMjIyBTdGF0aXN0aWNhbCBUcmFuc2Zvcm1hdGlvbnMNCg0KYGBge3Igc3RhdGlzdGljYWwtdHJhbnNmb3Jtc30NCiMgVXNpbmcgc3RhdF9zdW1tYXJ5IGZvciBhZ2dyZWdhdGlvbg0KcF9zdGF0IDwtIGdncGxvdChiaWdfZGlhbW9uZHMsIGFlcyh4ID0gY3V0LCB5ID0gcHJpY2UpKSArDQogIHN0YXRfc3VtbWFyeShmdW4gPSBtZWFuLCBnZW9tID0gInBvaW50Iiwgc2l6ZSA9IDMsIGNvbG9yID0gInJlZCIpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi5kYXRhID0gbWVhbl9jbF9ub3JtYWwsIA0KICAgICAgICAgICAgICAgZ2VvbSA9ICJlcnJvcmJhciIsIA0KICAgICAgICAgICAgICAgd2lkdGggPSAwLjIsDQogICAgICAgICAgICAgICBjb2xvciA9ICJyZWQiKSArDQogIHN0YXRfc3VtbWFyeShmdW4gPSBtZWRpYW4sIGdlb20gPSAicG9pbnQiLCBzaXplID0gMywgY29sb3IgPSAiYmx1ZSIpICsNCiAgbGFicyh0aXRsZSA9ICJNZWFuIGFuZCBNZWRpYW4gUHJpY2VzIGJ5IEN1dCIsDQogICAgICAgc3VidGl0bGUgPSAiV2l0aCA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbHMgZm9yIG1lYW5zIiwNCiAgICAgICB4ID0gIkN1dCBRdWFsaXR5IiwNCiAgICAgICB5ID0gIlByaWNlIChVU0QpIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gZG9sbGFyKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpwcmludChwX3N0YXQpDQpgYGANCg0KIyMjIENvbWJpbmluZyBNdWx0aXBsZSBQbG90cyB3aXRoIHBhdGNod29yaw0KDQpgYGB7ciBwYXRjaHdvcmstY29tYmluZX0NCmxpYnJhcnkocGF0Y2h3b3JrKQ0KDQojIENyZWF0ZSBpbmRpdmlkdWFsIHBsb3RzDQpwMSA8LSBnZ3Bsb3QoYmlnX2RpYW1vbmRzLCBhZXMoeCA9IGN1dCwgeSA9IHByaWNlKSkgKw0KICBnZW9tX2JveHBsb3QoZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGxhYnModGl0bGUgPSAiQm94IFBsb3QiLCB4ID0gTlVMTCwgeSA9ICJQcmljZSIpDQoNCnAyIDwtIGdncGxvdChiaWdfZGlhbW9uZHMsIGFlcyh4ID0gcHJpY2UsIGZpbGwgPSBjdXQpKSArDQogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNSkgKw0KICBsYWJzKHRpdGxlID0gIkRlbnNpdHkgUGxvdCIsIHggPSAiUHJpY2UiLCB5ID0gIkRlbnNpdHkiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCg0KcDMgPC0gZ2dwbG90KGJpZ19kaWFtb25kcywgYWVzKHggPSBjdXQsIGZpbGwgPSBjdXQpKSArDQogIGdlb21fYmFyKCkgKw0KICBsYWJzKHRpdGxlID0gIkJhciBQbG90IiwgeCA9ICJDdXQiLCB5ID0gIkNvdW50IikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQoNCnA0IDwtIGdncGxvdChiaWdfZGlhbW9uZHNbc2FtcGxlKC5OLCAxMDAwKV0sIA0KICAgICAgICAgICAgIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgY29sb3IgPSBjdXQpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgbGFicyh0aXRsZSA9ICJTY2F0dGVyIFBsb3QiLCB4ID0gIkNhcmF0IiwgeSA9ICJQcmljZSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQojIENvbWJpbmUgcGxvdHMNCihwMSArIHAyKSAvIChwMyArIHA0KSArDQogIHBsb3RfYW5ub3RhdGlvbih0aXRsZSA9ICJNdWx0aS1wYW5lbCBWaXN1YWxpemF0aW9uIG9mIERpYW1vbmQgRGF0YSIsDQogICAgICAgICAgICAgICAgICB0aGVtZSA9IHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemUgPSAxNCkpKQ0KYGBgDQoNCiMgNS4gSW50ZXJhY3RpdmUgVmlzdWFsaXphdGlvbiB3aXRoIHBsb3RseQ0KDQpwbG90bHkgaXMgYW4gaW50ZXJhY3RpdmUgdmlzdWFsaXphdGlvbiBsaWJyYXJ5IHRoYXQgd29ya3Mgc2VhbWxlc3NseSB3aXRoIGdncGxvdDIgYW5kIGNhbiBoYW5kbGUgbGFyZ2UgZGF0YXNldHMgZWZmaWNpZW50bHkuDQoNCiMjIDUuMSBDb252ZXJ0aW5nIGdncGxvdDIgdG8gcGxvdGx5DQoNCmBgYHtyIGdncGxvdC10by1wbG90bHl9DQojIENyZWF0ZSBhIGdncGxvdA0KcF9nZyA8LSBnZ3Bsb3QoYmlnX2RpYW1vbmRzW3NhbXBsZSguTiwgMjAwMCldLCANCiAgICAgICAgICAgICAgIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSwgDQogICAgICAgICAgICAgICAgICAgY29sb3IgPSBjdXQsIHNpemUgPSBkZXB0aCwNCiAgICAgICAgICAgICAgICAgICB0ZXh0ID0gcGFzdGUoIkN1dDoiLCBjdXQsICI8YnI+IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNvbG9yOiIsIGNvbG9yLCAiPGJyPiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDbGFyaXR5OiIsIGNsYXJpdHksICI8YnI+IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlByaWNlOiAkIiwgcHJpY2UpKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC42KSArDQogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDIiKSArDQogIGxhYnModGl0bGUgPSAiSW50ZXJhY3RpdmUgRGlhbW9uZCBFeHBsb3JlciIsDQogICAgICAgeCA9ICJDYXJhdCIsDQogICAgICAgeSA9ICJQcmljZSAoVVNEKSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCiMgQ29udmVydCB0byBwbG90bHkNCnBfcGxvdGx5IDwtIGdncGxvdGx5KHBfZ2csIHRvb2x0aXAgPSAidGV4dCIpICU+JQ0KICBsYXlvdXQoaG92ZXJsYWJlbCA9IGxpc3QoYmdjb2xvciA9ICJ3aGl0ZSIpLA0KICAgICAgICAgdGl0bGUgPSBsaXN0KHRleHQgPSAiSW50ZXJhY3RpdmUgRGlhbW9uZCBFeHBsb3Jlcjxicj48c3ViPkhvdmVyIGZvciBkZXRhaWxzPC9zdWI+IikpDQoNCiMgRGlzcGxheQ0KcF9wbG90bHkNCmBgYA0KDQojIyA1LjIgRGlyZWN0IHBsb3RseSBWaXN1YWxpemF0aW9ucw0KDQojIyMgSW50ZXJhY3RpdmUgU2NhdHRlciBQbG90DQoNCmBgYHtyIHBsb3RseS1kaXJlY3R9DQojIENyZWF0ZSBpbnRlcmFjdGl2ZSBzY2F0dGVyIHBsb3QgZGlyZWN0bHkgd2l0aCBwbG90bHkNCmRpcmVjdF9wbG90IDwtIHBsb3RfbHkoDQogIGRhdGEgPSBiaWdfZGlhbW9uZHNbc2FtcGxlKC5OLCAzMDAwKV0sDQogIHggPSB+Y2FyYXQsDQogIHkgPSB+cHJpY2UsDQogIGNvbG9yID0gfmN1dCwNCiAgY29sb3JzID0gIlNldDIiLA0KICB0eXBlID0gInNjYXR0ZXIiLA0KICBtb2RlID0gIm1hcmtlcnMiLA0KICBtYXJrZXIgPSBsaXN0KHNpemUgPSA4LCBvcGFjaXR5ID0gMC42KSwNCiAgdGV4dCA9IH5wYXN0ZSgiQ3V0OiIsIGN1dCwgIjxicj4iLA0KICAgICAgICAgICAgICAgICJDb2xvcjoiLCBjb2xvciwgIjxicj4iLA0KICAgICAgICAgICAgICAgICJDbGFyaXR5OiIsIGNsYXJpdHksICI8YnI+IiwNCiAgICAgICAgICAgICAgICAiQ2FyYXQ6Iiwgcm91bmQoY2FyYXQsIDIpLCAiPGJyPiIsDQogICAgICAgICAgICAgICAgIlByaWNlOiAkIiwgZm9ybWF0KHByaWNlLCBiaWcubWFyayA9ICIsIikpLA0KICBob3ZlcmluZm8gPSAidGV4dCINCikgJT4lDQogIGxheW91dCgNCiAgICB0aXRsZSA9ICJJbnRlcmFjdGl2ZSBEaWFtb25kIFByaWNlIEV4cGxvcmVyIiwNCiAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiQ2FyYXQiKSwNCiAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiUHJpY2UgKFVTRCkiKSwNCiAgICBob3ZlcmxhYmVsID0gbGlzdChiZ2NvbG9yID0gIndoaXRlIiwgZm9udCA9IGxpc3Qoc2l6ZSA9IDEyKSkNCiAgKQ0KDQpkaXJlY3RfcGxvdA0KYGBgDQoNCiMjIyAzRCBTY2F0dGVyIFBsb3QNCg0KYGBge3IgcGxvdGx5LTNkfQ0KIyAzRCBzY2F0dGVyIHBsb3QNCnBsb3RfM2QgPC0gcGxvdF9seSgNCiAgZGF0YSA9IGJpZ19kaWFtb25kc1tzYW1wbGUoLk4sIDIwMDApXSwNCiAgeCA9IH5jYXJhdCwNCiAgeSA9IH5kZXB0aCwNCiAgeiA9IH5wcmljZSwNCiAgY29sb3IgPSB+Y3V0LA0KICBjb2xvcnMgPSAiU2V0MiIsDQogIHR5cGUgPSAic2NhdHRlcjNkIiwNCiAgbW9kZSA9ICJtYXJrZXJzIiwNCiAgbWFya2VyID0gbGlzdChzaXplID0gNCwgb3BhY2l0eSA9IDAuNyksDQogIHRleHQgPSB+cGFzdGUoIkN1dDoiLCBjdXQsICI8YnI+UHJpY2U6ICQiLCBwcmljZSksDQogIGhvdmVyaW5mbyA9ICJ0ZXh0Ig0KKSAlPiUNCiAgbGF5b3V0KA0KICAgIHRpdGxlID0gIjNEIERpYW1vbmQgQW5hbHlzaXMiLA0KICAgIHNjZW5lID0gbGlzdCgNCiAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJDYXJhdCIpLA0KICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIkRlcHRoICglKSIpLA0KICAgICAgemF4aXMgPSBsaXN0KHRpdGxlID0gIlByaWNlIChVU0QpIikNCiAgICApDQogICkNCg0KcGxvdF8zZA0KYGBgDQoNCiMjIyBJbnRlcmFjdGl2ZSBIaXN0b2dyYW0NCg0KYGBge3IgcGxvdGx5LWhpc3RvZ3JhbX0NCiMgSW50ZXJhY3RpdmUgaGlzdG9ncmFtIHdpdGggbXVsdGlwbGUgdHJhY2VzDQpwbG90bHlfaGlzdCA8LSBwbG90X2x5KGFscGhhID0gMC42KQ0KDQojIEFkZCB0cmFjZXMgZm9yIGVhY2ggY3V0DQpjdXRzIDwtIHVuaXF1ZShiaWdfZGlhbW9uZHMkY3V0KQ0KY29sb3JzIDwtIGJyZXdlci5wYWwobGVuZ3RoKGN1dHMpLCAiU2V0MiIpDQoNCmZvciAoaSBpbiBzZXFfYWxvbmcoY3V0cykpIHsNCiAgcGxvdGx5X2hpc3QgPC0gcGxvdGx5X2hpc3QgJT4lDQogICAgYWRkX2hpc3RvZ3JhbSgNCiAgICAgIHggPSBiaWdfZGlhbW9uZHNbY3V0ID09IGN1dHNbaV1dJHByaWNlLA0KICAgICAgbmFtZSA9IGN1dHNbaV0sDQogICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gY29sb3JzW2ldKSwNCiAgICAgIG9wYWNpdHkgPSAwLjYNCiAgICApDQp9DQoNCnBsb3RseV9oaXN0IDwtIHBsb3RseV9oaXN0ICU+JQ0KICBsYXlvdXQoDQogICAgdGl0bGUgPSAiSW50ZXJhY3RpdmUgUHJpY2UgRGlzdHJpYnV0aW9uIGJ5IEN1dCIsDQogICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIlByaWNlIChVU0QpIiksDQogICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIkNvdW50IiksDQogICAgYmFybW9kZSA9ICJvdmVybGF5IiwNCiAgICBob3Zlcm1vZGUgPSAieCB1bmlmaWVkIg0KICApDQoNCnBsb3RseV9oaXN0DQpgYGANCg0KIyMgNS4zIEFkdmFuY2VkIHBsb3RseSBGZWF0dXJlcw0KDQojIyMgTGlua2VkIEJydXNoaW5nDQoNCmBgYHtyIHBsb3RseS1saW5rZWR9DQojIENyZWF0ZSBzdWJwbG90cyB3aXRoIGxpbmtlZCBicnVzaGluZw0KZmlnMSA8LSBwbG90X2x5KA0KICBkYXRhID0gYmlnX2RpYW1vbmRzW3NhbXBsZSguTiwgMjAwMCldLA0KICB4ID0gfmNhcmF0LA0KICB5ID0gfnByaWNlLA0KICBjb2xvciA9IH5jdXQsDQogIHR5cGUgPSAic2NhdHRlciIsDQogIG1vZGUgPSAibWFya2VycyIsDQogIHNvdXJjZSA9ICJBIg0KKQ0KDQpmaWcyIDwtIHBsb3RfbHkoDQogIGRhdGEgPSBiaWdfZGlhbW9uZHNbc2FtcGxlKC5OLCAyMDAwKV0sDQogIHggPSB+ZGVwdGgsDQogIHkgPSB+dGFibGUsDQogIGNvbG9yID0gfmN1dCwNCiAgdHlwZSA9ICJzY2F0dGVyIiwNCiAgbW9kZSA9ICJtYXJrZXJzIiwNCiAgc291cmNlID0gIkEiDQopDQoNCmxpbmtlZF9wbG90IDwtIHN1YnBsb3QoZmlnMSwgZmlnMiwgdGl0bGVYID0gVFJVRSwgdGl0bGVZID0gVFJVRSkgJT4lDQogIGxheW91dCgNCiAgICB0aXRsZSA9ICJMaW5rZWQgQnJ1c2hpbmc6IFNlbGVjdCBwb2ludHMgaW4gb25lIHBsb3QgdG8gaGlnaGxpZ2h0IGluIHRoZSBvdGhlciIsDQogICAgc2hvd2xlZ2VuZCA9IEZBTFNFDQogICkgJT4lDQogIGhpZ2hsaWdodCgNCiAgICBvbiA9ICJwbG90bHlfc2VsZWN0ZWQiLA0KICAgIG9mZiA9ICJwbG90bHlfZGVzZWxlY3QiLA0KICAgIHBlcnNpc3RlbnQgPSBGQUxTRQ0KICApDQoNCmxpbmtlZF9wbG90DQpgYGANCg0KIyMjIERhc2hib2FyZHMgd2l0aCBTdWJwbG90cw0KDQpgYGB7ciBwbG90bHktZGFzaGJvYXJkfQ0KIyBDcmVhdGUgYSBkYXNoYm9hcmQgd2l0aCBtdWx0aXBsZSBwbG90cw0KbGlicmFyeShwbG90bHkpDQoNCiMgUGxvdCAxOiBTY2F0dGVyDQpwMSA8LSBwbG90X2x5KGJpZ19kaWFtb25kc1tzYW1wbGUoLk4sIDEwMDApXSwgDQogICAgICAgICAgICAgIHggPSB+Y2FyYXQsIHkgPSB+cHJpY2UsIA0KICAgICAgICAgICAgICBjb2xvciA9IH5jdXQsIHR5cGUgPSAic2NhdHRlciIsIG1vZGUgPSAibWFya2VycyIsDQogICAgICAgICAgICAgIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IDYsIG9wYWNpdHkgPSAwLjcpKSAlPiUNCiAgbGF5b3V0KHhheGlzID0gbGlzdCh0aXRsZSA9ICJDYXJhdCIpLA0KICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIlByaWNlIikpDQoNCiMgUGxvdCAyOiBCb3ggcGxvdA0KcDIgPC0gcGxvdF9seShiaWdfZGlhbW9uZHMsIA0KICAgICAgICAgICAgICB4ID0gfmN1dCwgeSA9IH5wcmljZSwgDQogICAgICAgICAgICAgIGNvbG9yID0gfmN1dCwgdHlwZSA9ICJib3giKSAlPiUNCiAgbGF5b3V0KHhheGlzID0gbGlzdCh0aXRsZSA9ICJDdXQiKSwNCiAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJQcmljZSIpLA0KICAgICAgICAgc2hvd2xlZ2VuZCA9IEZBTFNFKQ0KDQojIFBsb3QgMzogSGlzdG9ncmFtDQpwMyA8LSBwbG90X2x5KHggPSBiaWdfZGlhbW9uZHMkcHJpY2UsIA0KICAgICAgICAgICAgICB0eXBlID0gImhpc3RvZ3JhbSIsDQogICAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAic3RlZWxibHVlIikpICU+JQ0KICBsYXlvdXQoeGF4aXMgPSBsaXN0KHRpdGxlID0gIlByaWNlIiksDQogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiQ291bnQiKSkNCg0KIyBQbG90IDQ6IEJhciBjaGFydA0KY3V0X2NvdW50cyA8LSBiaWdfZGlhbW9uZHNbLCAuTiwgYnkgPSBjdXRdDQpwNCA8LSBwbG90X2x5KGN1dF9jb3VudHMsIA0KICAgICAgICAgICAgICB4ID0gfmN1dCwgeSA9IH5OLCANCiAgICAgICAgICAgICAgdHlwZSA9ICJiYXIiLA0KICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gYnJld2VyLnBhbCg1LCAiU2V0MyIpKSkgJT4lDQogIGxheW91dCh4YXhpcyA9IGxpc3QodGl0bGUgPSAiQ3V0IiksDQogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiQ291bnQiKSkNCg0KIyBDb21iaW5lIGludG8gZGFzaGJvYXJkDQpkYXNoYm9hcmQgPC0gc3VicGxvdChwMSwgcDIsIHAzLCBwNCwgDQogICAgICAgICAgICAgICAgICAgICBucm93cyA9IDIsIA0KICAgICAgICAgICAgICAgICAgICAgc2hhcmVYID0gRkFMU0UsIA0KICAgICAgICAgICAgICAgICAgICAgc2hhcmVZID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICB0aXRsZVggPSBUUlVFLCANCiAgICAgICAgICAgICAgICAgICAgIHRpdGxlWSA9IFRSVUUpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAiRGlhbW9uZCBEYXRhIERhc2hib2FyZCIsDQogICAgICAgICBzaG93bGVnZW5kID0gVFJVRSkNCg0KZGFzaGJvYXJkDQpgYGANCg0KIyA2LiBQZXJmb3JtYW5jZSBDb21wYXJpc29uOiBCYXNlIFIgdnMgZ2dwbG90MiB2cyBwbG90bHkNCg0KTGV0J3MgY29tcGFyZSB0aGUgcGVyZm9ybWFuY2UgYW5kIGZlYXR1cmVzIG9mIGRpZmZlcmVudCB2aXN1YWxpemF0aW9uIGFwcHJvYWNoZXMuDQoNCmBgYHtyIHBlcmZvcm1hbmNlLWNvbXBhcmlzb259DQojIEZ1bmN0aW9uIHRvIHRpbWUgcGxvdCBjcmVhdGlvbg0KdGltZV9wbG90X2NyZWF0aW9uIDwtIGZ1bmN0aW9uKHBsb3RfZnVuYywgaXRlcmF0aW9ucyA9IDEwKSB7DQogIHRpbWVzIDwtIG51bWVyaWMoaXRlcmF0aW9ucykNCiAgZm9yIChpIGluIDE6aXRlcmF0aW9ucykgew0KICAgIHN0YXJ0X3RpbWUgPC0gU3lzLnRpbWUoKQ0KICAgIHBsb3RfZnVuYygpDQogICAgdGltZXNbaV0gPC0gYXMubnVtZXJpYyhTeXMudGltZSgpIC0gc3RhcnRfdGltZSkNCiAgfQ0KICByZXR1cm4obWVhbih0aW1lcykpDQp9DQoNCiMgRGVmaW5lIHBsb3QgZnVuY3Rpb25zDQpiYXNlX3JfcGxvdCA8LSBmdW5jdGlvbigpIHsNCiAgcGFyKG1mcm93ID0gYygxLCAxKSkNCiAgcGxvdChiaWdfZGlhbW9uZHNbc2FtcGxlKC5OLCA1MDAwKV0kY2FyYXQsDQogICAgICAgYmlnX2RpYW1vbmRzW3NhbXBsZSguTiwgNTAwMCldJHByaWNlLA0KICAgICAgIG1haW4gPSAiQmFzZSBSIFBsb3QiLA0KICAgICAgIHhsYWIgPSAiQ2FyYXQiLCB5bGFiID0gIlByaWNlIiwNCiAgICAgICBwY2ggPSAxNiwgY29sID0gYWxwaGEoImJsdWUiLCAwLjMpKQ0KfQ0KDQpnZ3Bsb3RfcGxvdCA8LSBmdW5jdGlvbigpIHsNCiAgcCA8LSBnZ3Bsb3QoYmlnX2RpYW1vbmRzW3NhbXBsZSguTiwgNTAwMCldLCANCiAgICAgICAgICAgICAgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlKSkgKw0KICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjMsIGNvbG9yID0gImJsdWUiKSArDQogICAgbGFicyh0aXRsZSA9ICJnZ3Bsb3QyIFBsb3QiLA0KICAgICAgICAgeCA9ICJDYXJhdCIsIHkgPSAiUHJpY2UiKQ0KICBwcmludChwKQ0KfQ0KDQojIFRpbWUgdGhlIHBsb3RzDQpzZXQuc2VlZCgxMjMpDQpiYXNlX3RpbWUgPC0gdGltZV9wbG90X2NyZWF0aW9uKGJhc2Vfcl9wbG90LCA1KQ0KZ2dwbG90X3RpbWUgPC0gdGltZV9wbG90X2NyZWF0aW9uKGdncGxvdF9wbG90LCA1KQ0KDQojIENyZWF0ZSBjb21wYXJpc29uIGRhdGENCmNvbXBhcmlzb25fZGF0YSA8LSBkYXRhLmZyYW1lKA0KICBNZXRob2QgPSBjKCJCYXNlIFIiLCAiZ2dwbG90MiIsICJwbG90bHkgKHN0YXRpYykiLCAicGxvdGx5IChpbnRlcmFjdGl2ZSkiKSwNCiAgU3BlZWQgPSBjKGJhc2VfdGltZSwgZ2dwbG90X3RpbWUsIGdncGxvdF90aW1lICogMS41LCBnZ3Bsb3RfdGltZSAqIDIpLA0KICBJbnRlcmFjdGl2aXR5ID0gYygiTm9uZSIsICJOb25lIiwgIkhpZ2giLCAiSGlnaCIpLA0KICBBZXN0aGV0aWNzID0gYygiQmFzaWMiLCAiRXhjZWxsZW50IiwgIkV4Y2VsbGVudCIsICJFeGNlbGxlbnQiKSwNCiAgTGVhcm5pbmdfQ3VydmUgPSBjKCJMb3ciLCAiTWVkaXVtIiwgIk1lZGl1bSIsICJNZWRpdW0iKSwNCiAgQmVzdF9Gb3IgPSBjKCJRdWljayBleHBsb3JhdGlvbiIsICJQdWJsaWNhdGlvbiBwbG90cyIsIA0KICAgICAgICAgICAgICAgIldlYiBkYXNoYm9hcmRzIiwgIkludGVyYWN0aXZlIHJlcG9ydHMiKQ0KKQ0KDQojIERpc3BsYXkgY29tcGFyaXNvbiB0YWJsZQ0KRFQ6OmRhdGF0YWJsZShjb21wYXJpc29uX2RhdGEsDQogICAgICAgICAgICAgIG9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSAxMCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRvbSA9ICd0JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sdW1uRGVmcyA9IGxpc3QoDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdChjbGFzc05hbWUgPSAnZHQtY2VudGVyJywgdGFyZ2V0cyA9IDE6NSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSksDQogICAgICAgICAgICAgIHJvd25hbWVzID0gRkFMU0UsDQogICAgICAgICAgICAgIGNhcHRpb24gPSAiVmlzdWFsaXphdGlvbiBNZXRob2QgQ29tcGFyaXNvbiIpDQpgYGANCg0KIyMgVmlzdWFsIEZlYXR1cmUgQ29tcGFyaXNvbg0KDQpgYGB7ciBmZWF0dXJlLWNvbXBhcmlzb259DQojIENyZWF0ZSBhIHZpc3VhbGl6YXRpb24gb2YgZmVhdHVyZSBjb21wYXJpc29uDQpmZWF0dXJlcyA8LSBkYXRhLmZyYW1lKA0KICBGZWF0dXJlID0gcmVwKGMoIlNwZWVkIiwgIkN1c3RvbWl6YXRpb24iLCAiSW50ZXJhY3Rpdml0eSIsIA0KICAgICAgICAgICAgICAgICAgIjNEIFN1cHBvcnQiLCAiQW5pbWF0aW9uIiwgIkV4cG9ydCBRdWFsaXR5IiksIDMpLA0KICBTY29yZSA9IGMoOSwgNiwgMiwgMywgMSwgNywgICAjIEJhc2UgUg0KICAgICAgICAgICAgNywgOSwgMywgNCwgNCwgOSwgICAjIGdncGxvdDINCiAgICAgICAgICAgIDUsIDgsIDEwLCA5LCA5LCA4KSwgIyBwbG90bHkNCiAgTWV0aG9kID0gcmVwKGMoIkJhc2UgUiIsICJnZ3Bsb3QyIiwgInBsb3RseSIpLCBlYWNoID0gNikNCikNCg0KcF9mZWF0dXJlcyA8LSBnZ3Bsb3QoZmVhdHVyZXMsIGFlcyh4ID0gRmVhdHVyZSwgeSA9IFNjb3JlLCBmaWxsID0gTWV0aG9kKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiLCB3aWR0aCA9IDAuNykgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gU2NvcmUpLCANCiAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAwLjcpLA0KICAgICAgICAgICAgdmp1c3QgPSAtMC41LCBzaXplID0gMykgKw0KICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlNldDIiKSArDQogIGxhYnModGl0bGUgPSAiVmlzdWFsaXphdGlvbiBNZXRob2QgRmVhdHVyZSBDb21wYXJpc29uIiwNCiAgICAgICBzdWJ0aXRsZSA9ICJTY29yZSBvdXQgb2YgMTAgZm9yIGVhY2ggZmVhdHVyZSIsDQogICAgICAgeCA9IE5VTEwsDQogICAgICAgeSA9ICJTY29yZSIsDQogICAgICAgZmlsbCA9ICJNZXRob2QiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQoNCnByaW50KHBfZmVhdHVyZXMpDQpgYGANCg0KIyA3LiBCZXN0IFByYWN0aWNlcyBmb3IgQmlnIERhdGEgVmlzdWFsaXphdGlvbg0KDQojIyA3LjEgV2hlbiB0byBVc2UgRWFjaCBNZXRob2QNCg0KKipCYXNlIFIgR3JhcGhpY3M6KioNCi0gUXVpY2sgZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcw0KLSBTaW1wbGUgZGlhZ25vc3RpYyBwbG90cw0KLSBXaGVuIHdvcmtpbmcgaW4gbWluaW1hbCBlbnZpcm9ubWVudHMNCi0gRm9yIHZlcnkgYmFzaWMsIG5vLWZyaWxscyB2aXN1YWxpemF0aW9ucw0KDQoqKmdncGxvdDI6KioNCi0gUHVibGljYXRpb24tcXVhbGl0eSBzdGF0aWMgZ3JhcGhpY3MNCi0gQ29tcGxleCBtdWx0aS1sYXllcmVkIHZpc3VhbGl6YXRpb25zDQotIFdoZW4gY29uc2lzdGVuY3kgYWNyb3NzIHBsb3RzIGlzIGltcG9ydGFudA0KLSBGb3IgZGV0YWlsZWQgY3VzdG9taXphdGlvbiBhbmQgdGhlbWluZw0KDQoqKnBsb3RseToqKg0KLSBJbnRlcmFjdGl2ZSB3ZWIgYXBwbGljYXRpb25zIGFuZCBkYXNoYm9hcmRzDQotIFdoZW4gdXNlcnMgbmVlZCB0byBleHBsb3JlIGRhdGEgZHluYW1pY2FsbHkNCi0gRm9yIDNEIHZpc3VhbGl6YXRpb25zDQotIFdoZW4gc2hhcmluZyB2aXN1YWxpemF0aW9ucyBvbmxpbmUNCg0KIyMgNy4yIE9wdGltaXphdGlvbiBUaXBzDQoNCiMjIyBGb3IgZ2dwbG90MjoNCmBgYHtyIG9wdGltaXphdGlvbi10aXBzLCBldmFsPUZBTFNFfQ0KIyAxLiBVc2Ugc2FtcGxpbmcgZm9yIGxhcmdlIGRhdGFzZXRzDQpnZ3Bsb3QoYmlnX2RpYW1vbmRzW3NhbXBsZSguTiwgMTAwMDApXSwgYWVzKHgsIHkpKSArIGdlb21fcG9pbnQoKQ0KDQojIDIuIFVzZSBiaW5uaW5nIGZvciBkZW5zZSBkYXRhDQpnZ3Bsb3QoYmlnX2RpYW1vbmRzLCBhZXMoeCwgeSkpICsgZ2VvbV9iaW4yZChiaW5zID0gMTAwKQ0KDQojIDMuIFVzZSBkZW5zaXR5IHBsb3RzIGluc3RlYWQgb2Ygc2NhdHRlciBwbG90cw0KZ2dwbG90KGJpZ19kaWFtb25kcywgYWVzKHgsIGZpbGwgPSBncm91cCkpICsgZ2VvbV9kZW5zaXR5KGFscGhhID0gMC41KQ0KDQojIDQuIEF2b2lkIG92ZXJwbG90dGluZyB3aXRoIHRyYW5zcGFyZW5jeQ0KZ2dwbG90KGRhdGEsIGFlcyh4LCB5KSkgKyBnZW9tX3BvaW50KGFscGhhID0gMC4xKQ0KDQojIDUuIFVzZSBlZmZpY2llbnQgZ2VvbWV0cmllcw0KIyBnZW9tX2hleCgpIGlzIG9mdGVuIGZhc3RlciB0aGFuIGdlb21fcG9pbnQoKSBmb3IgbGFyZ2UgZGF0YQ0KYGBgDQoNCiMjIyBGb3IgcGxvdGx5Og0KYGBge3IgcGxvdGx5LW9wdGltaXphdGlvbiwgZXZhbD1GQUxTRX0NCiMgMS4gTGltaXQgZGF0YSBwb2ludHMgZm9yIHNjYXR0ZXIgcGxvdHMNCnBsb3RfbHkoZGF0YVtzYW1wbGUoLk4sIDEwMDAwKV0sIHggPSB+eCwgeSA9IH55KQ0KDQojIDIuIFVzZSBXZWJHTCBmb3IgdmVyeSBsYXJnZSBkYXRhc2V0cw0KcGxvdF9seShkYXRhLCB4ID0gfngsIHkgPSB+eSwgdHlwZSA9ICdzY2F0dGVyZ2wnKQ0KDQojIDMuIEFnZ3JlZ2F0ZSBkYXRhIGJlZm9yZSBwbG90dGluZw0KYWdncmVnYXRlZCA8LSBkYXRhWywgLihtZWFuX3kgPSBtZWFuKHkpKSwgYnkgPSB4XQ0KcGxvdF9seShhZ2dyZWdhdGVkLCB4ID0gfngsIHkgPSB+bWVhbl95KQ0KDQojIDQuIFVzZSBzZXJ2ZXItc2lkZSBwcm9jZXNzaW5nIGZvciBtYXNzaXZlIGRhdGFzZXRzDQojIENvbnNpZGVyIHNoaW55IG9yIGRhc2ggYXBwbGljYXRpb25zDQpgYGANCg0KIyMgNy4zIE1lbW9yeSBNYW5hZ2VtZW50DQoNCmBgYHtyIG1lbW9yeS1tYW5hZ2VtZW50fQ0KIyBGdW5jdGlvbiB0byBtb25pdG9yIG1lbW9yeSBkdXJpbmcgdmlzdWFsaXphdGlvbg0KbW9uaXRvcl92aXpfbWVtb3J5IDwtIGZ1bmN0aW9uKHZpel9mdW5jLCBmdW5jX25hbWUpIHsNCiAgbWVtX2JlZm9yZSA8LSBwcnlyOjptZW1fdXNlZCgpDQogIHZpel9mdW5jKCkNCiAgbWVtX2FmdGVyIDwtIHByeXI6Om1lbV91c2VkKCkNCiAgDQogIHJldHVybihkYXRhLmZyYW1lKA0KICAgIE1ldGhvZCA9IGZ1bmNfbmFtZSwNCiAgICBNZW1vcnlfVXNlZF9NQiA9IHJvdW5kKChtZW1fYWZ0ZXIgLSBtZW1fYmVmb3JlKSAvIDEwMjReMiwgMiksDQogICAgTWVtb3J5X1VzZWRfR0IgPSByb3VuZCgobWVtX2FmdGVyIC0gbWVtX2JlZm9yZSkgLyAxMDI0XjMsIDMpDQogICkpDQp9DQoNCiMgVGVzdCBtZW1vcnkgdXNhZ2UNCm1lbW9yeV9yZXN1bHRzIDwtIHJiaW5kKA0KICBtb25pdG9yX3Zpel9tZW1vcnkoYmFzZV9yX3Bsb3QsICJCYXNlIFIiKSwNCiAgbW9uaXRvcl92aXpfbWVtb3J5KGdncGxvdF9wbG90LCAiZ2dwbG90MiIpDQopDQoNCnByaW50KG1lbW9yeV9yZXN1bHRzKQ0KYGBgDQoNCiMgUHJhY3RpY2FsIEV4ZXJjaXNlcw0KDQojIyBFeGVyY2lzZSAxOiBEaWFtb25kIFByaWNlIEFuYWx5c2lzDQoNCkNyZWF0ZSBhIGNvbXByZWhlbnNpdmUgdmlzdWFsaXphdGlvbiBkYXNoYm9hcmQgdGhhdCBpbmNsdWRlczoNCg0KMS4gQSBzY2F0dGVyIHBsb3Qgc2hvd2luZyBjYXJhdCB2cyBwcmljZSwgY29sb3JlZCBieSBjdXQsIHdpdGggc21vb3RoZWQgdHJlbmQgbGluZXMNCjIuIEEgc2V0IG9mIGZhY2V0ZWQgaGlzdG9ncmFtcyBzaG93aW5nIHByaWNlIGRpc3RyaWJ1dGlvbiBmb3IgZWFjaCBjbGFyaXR5IGdyYWRlDQozLiBBbiBpbnRlcmFjdGl2ZSBwbG90bHkgdmlzdWFsaXphdGlvbiB0aGF0IGFsbG93cyB1c2VycyB0byBzZWxlY3QgYSBwcmljZSByYW5nZSBhbmQgc2VlIHRoZSBjb3JyZXNwb25kaW5nIGRpYW1vbmRzDQo0LiBBIHN1bW1hcnkgdGFibGUgc2hvd2luZyBhdmVyYWdlIHByaWNlIGJ5IGN1dCBhbmQgY29sb3IgY29tYmluYXRpb24NCg0KIyMgRXhlcmNpc2UgMjogUGVyZm9ybWFuY2UgT3B0aW1pemF0aW9uDQoNClRha2UgYSBsYXJnZSBkYXRhc2V0IChvciBzaW11bGF0ZSBvbmUgd2l0aCAxIG1pbGxpb24gcm93cykgYW5kOg0KDQoxLiBDcmVhdGUgdGhyZWUgdmVyc2lvbnMgb2YgdGhlIHNhbWUgc2NhdHRlciBwbG90IHVzaW5nOg0KICAgLSBBbGwgZGF0YSBwb2ludHMgd2l0aCB0cmFuc3BhcmVuY3kNCiAgIC0gMSUgcmFuZG9tIHNhbXBsaW5nDQogICAtIEhleGFnb25hbCBiaW5uaW5nDQogICANCjIuIENvbXBhcmUgdGhlIGNyZWF0aW9uIHRpbWUgYW5kIG1lbW9yeSB1c2FnZSBvZiBlYWNoIGFwcHJvYWNoDQozLiBDcmVhdGUgYW4gaW50ZXJhY3RpdmUgdmVyc2lvbiB1c2luZyBwbG90bHkgd2l0aCBhIHNsaWRlciB0byBhZGp1c3QgdGhlIHNhbXBsZSBzaXplDQoNCiMjIEV4ZXJjaXNlIDM6IEludGVyYWN0aXZlIERhc2hib2FyZA0KDQpVc2luZyB0aGUgZGlhbW9uZHMgZGF0YXNldCwgY3JlYXRlIGFuIGludGVyYWN0aXZlIGRhc2hib2FyZCB3aXRoOg0KDQoxLiBBIG1haW4gc2NhdHRlciBwbG90IG9mIGNhcmF0IHZzIHByaWNlDQoyLiBDb250cm9scyB0byBmaWx0ZXIgYnkgY3V0LCBjb2xvciwgYW5kIGNsYXJpdHkNCjMuIEEgaGlzdG9ncmFtIHNob3dpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgc2VsZWN0ZWQgZGF0YQ0KNC4gQSBzdW1tYXJ5IHN0YXRpc3RpY3MgcGFuZWwNCjUuIEV4cG9ydCBmdW5jdGlvbmFsaXR5IGZvciB0aGUgZmlsdGVyZWQgZGF0YQ0KDQoNCiMjIEtleSBUYWtlYXdheXMNCg0KMS4gKipDaG9vc2UgdGhlIHJpZ2h0IHRvb2wgZm9yIHRoZSBqb2IqKjogQmFzZSBSIGZvciBxdWljayBsb29rcywgZ2dwbG90MiBmb3IgcHVibGljYXRpb24gZ3JhcGhpY3MsIHBsb3RseSBmb3IgaW50ZXJhY3Rpdml0eQ0KMi4gKipIYW5kbGUgbGFyZ2UgZGF0YXNldHMgc3RyYXRlZ2ljYWxseSoqOiBVc2Ugc2FtcGxpbmcsIGJpbm5pbmcsIGFuZCBhZ2dyZWdhdGlvbiB0byBtYW5hZ2UgcGVyZm9ybWFuY2UNCjMuICoqQ29uc2lkZXIgeW91ciBhdWRpZW5jZSoqOiBTdGF0aWMgcGxvdHMgZm9yIHJlcG9ydHMsIGludGVyYWN0aXZlIHBsb3RzIGZvciBleHBsb3JhdGlvbg0KNC4gKipPcHRpbWl6ZSBmb3IgcGVyZm9ybWFuY2UqKjogTW9uaXRvciBtZW1vcnkgdXNhZ2UgYW5kIHJlbmRlcmluZyB0aW1lcw0KNS4gKipJdGVyYXRlIGFuZCByZWZpbmUqKjogU3RhcnQgc2ltcGxlLCB0aGVuIGFkZCBjb21wbGV4aXR5IGFzIG5lZWRlZA0KDQojIyBOZXh0IFN0ZXBzDQoNCjEuIFByYWN0aWNlIHdpdGggeW91ciBvd24gZGF0YXNldHMNCjIuIEV4cGxvcmUgYWR2YW5jZWQgZ2dwbG90MiBleHRlbnNpb25zIChnZ3JlcGVsLCBnZ2FuaW1hdGUsIGdnZm9yY2UpDQozLiBMZWFybiB0byBjcmVhdGUgZGFzaGJvYXJkcyB3aXRoIHNoaW55ICsgcGxvdGx5DQo0LiBTdHVkeSBjb2xvciB0aGVvcnkgYW5kIGFjY2Vzc2liaWxpdHkgZm9yIGJldHRlciB2aXN1YWxpemF0aW9ucw0KNS4gSm9pbiB0aGUgZ2dwbG90MiBhbmQgcGxvdGx5IGNvbW11bml0aWVzIGZvciBvbmdvaW5nIGxlYXJuaW5nDQoNCiMgQWRkaXRpb25hbCBSZXNvdXJjZXMNCg0KLSAqKmdncGxvdDIgRG9jdW1lbnRhdGlvbioqOiBodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy8NCi0gKipwbG90bHkgUiBEb2N1bWVudGF0aW9uKio6IGh0dHBzOi8vcGxvdGx5LmNvbS9yLw0KLSAqKlIgR3JhcGggR2FsbGVyeSoqOiBodHRwczovL3d3dy5yLWdyYXBoLWdhbGxlcnkuY29tLw0KLSAqKkRhdGEgVmlzdWFsaXphdGlvbiB3aXRoIFIqKjogaHR0cHM6Ly9ya2FiYWNvZmYuZ2l0aHViLmlvL2RhdGF2aXMvDQotICoqRnVuZGFtZW50YWxzIG9mIERhdGEgVmlzdWFsaXphdGlvbioqOiBodHRwczovL2NsYXVzd2lsa2UuY29tL2RhdGF2aXovDQoNCg0KLS0tDQoNCioqVGhpcyBtYXRlcmlhbCBpcyBwYXJ0IG9mIHRoZSB0cmFpbmluZyBwcm9ncmFtIGJ5IFRoZSBOYXRpb25hbCBDZW50cmUgZm9yIFJlc2VhcmNoIE1ldGhvZHMgwqkgW05DUk1dKGh0dHBzOi8vd3d3Lm5jcm0uYWMudWsvYWJvdXQvKSBhdXRob3JlZCBieSBbRHLigK9Tb21uYXRo4oCvQ2hhdWRodXJpXShodHRwczovL3d3dy5zb3V0aGFtcHRvbi5hYy51ay9wZW9wbGUvNjVjdHE4L2RvY3Rvci1zb21uYXRoLWNoYXVkaHVyaSkgKFVuaXZlcnNpdHkgb2YgU291dGhhbXB0b24pLiBDb250ZW50IGlzIHVuZGVyIGEgQ0PigK9CWeKAkXN0eWxlIHBlcm1pc3NpdmUgbGljZW5zZSBhbmQgY2FuIGJlIGZyZWVseSB1c2VkIGZvciBlZHVjYXRpb25hbCBwdXJwb3NlcyB3aXRoIHByb3BlciBhdHRyaWJ1dGlvbi4qKg0K